package edu.stanford.bmir.protege.web.server.stkosutil.mapper;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Stack;

import org.semanticweb.owlapi.model.AddAxiom;
import org.semanticweb.owlapi.model.EntityType;
import org.semanticweb.owlapi.model.OWLAnnotation;
import org.semanticweb.owlapi.model.OWLAxiom;
import org.semanticweb.owlapi.model.OWLClass;
import org.semanticweb.owlapi.model.OWLDataFactory;
import org.semanticweb.owlapi.model.OWLLiteral;
import org.semanticweb.owlapi.model.OWLOntologyChange;
import org.semanticweb.owlapi.model.RemoveAxiom;
import org.semanticweb.owlapi.vocab.OWLRDFVocabulary;

import edu.stanford.bmir.protege.web.client.rpc.data.EntityData;
import edu.stanford.bmir.protege.web.client.rpc.data.SubclassEntityData;
import edu.stanford.bmir.protege.web.client.rpc.data.ValueType;
import edu.stanford.bmir.protege.web.client.rpc.stkosutil.common.MetaItemList;
import edu.stanford.bmir.protege.web.client.rpc.stkosutil.common.UtilConstants;
import edu.stanford.bmir.protege.web.client.rpc.stkosutil.mapper.MappingOptions;
import edu.stanford.bmir.protege.web.client.rpc.stkosutil.mapper.MappingResult;
import edu.stanford.bmir.protege.web.server.MetaProjectManager;
import edu.stanford.bmir.protege.web.server.owlapi.AssertedClassHierarchyProvider;
import edu.stanford.bmir.protege.web.server.owlapi.OWLAPIProject;
import edu.stanford.bmir.protege.web.server.owlapi.OWLAPIProjectManager;
import edu.stanford.bmir.protege.web.server.owlapi.OntologyServiceOWLAPIImpl;
import edu.stanford.bmir.protege.web.server.owlapi.RenderingManager;
import edu.stanford.bmir.protege.web.shared.project.ProjectId;
import edu.stanford.bmir.protege.web.shared.user.UserId;
import edu.stanford.bmir.protege.web.shared.watches.Watch;

public class ClassMapper {
	
	 /****
	  *The following functions are copied from OntologyServiceOWLAPIImpl as they are not visible
	  * 
	  */
	 private OWLAPIProject getProject(String projectName) {
	        if (projectName == null) {
	            throw new NullPointerException("projectName must not be null");
	        }
	        ProjectId projectId = ProjectId.get(projectName);
	        // TODO: Log
	        return getProject(projectId);
	    }

	    /**
	     * Gets the OWLAPIProject for a given {@link ProjectId}.If a project with the specified id exists then that project
	     * will be returned, otherwise, a fresh project will be created and that fresh project returned.
	     * @param projectId The id of the project.
	     * @return The OWL API project. Not <code>null</code>.
	     */
	    private OWLAPIProject getProject(ProjectId projectId) {
	        OWLAPIProjectManager pm = OWLAPIProjectManager.getProjectManager();
	        return pm.getProject(projectId);
	    }
	 //*******************   
	
	    
	 private List<OwlClassHolder> getSubclasses(OWLAPIProject project, String className) {
	        if (className == null) {
	            return Collections.emptyList();
	        }
	        List<OwlClassHolder> result = new ArrayList<OwlClassHolder>();
	       
	        RenderingManager rm = project.getRenderingManager();
	        AssertedClassHierarchyProvider hierarchyProvider = project.getClassHierarchyProvider();
	        OWLClass cls = rm.getEntity(className, EntityType.CLASS);

	        boolean checkForDeprecated = project.getRootOntology().containsAnnotationPropertyInSignature(OWLRDFVocabulary.OWL_DEPRECATED.getIRI());
	        for (OWLClass subclass : new ArrayList<OWLClass>(hierarchyProvider.getChildren(cls))) {
	            boolean deprecated = false;
//	            if(checkForDeprecated) {
//	                deprecated = project.isDeprecated(subclass);
//	            }
	        //if (!deprecated) {
	              //  Set<OWLClass> children = hierarchyProvider.getChildren(subclass);
	              //  int subClassSubClassesCount = children.size();
	                String browserText = rm.getBrowserText(subclass);
	                String name = subclass.getIRI().toString();
	                OwlClassHolder classHolder = new OwlClassHolder();;
	                classHolder.setBrowserText(browserText);
	                classHolder.setName(name);
	                classHolder.setOwlClass(subclass);
	                //data.setDeprecated(deprecated);

	                //data.setValueType(ValueType.Cls);
	                result.add(classHolder);
//	            }
	        }
	        Collections.sort(result, new Comparator<OwlClassHolder>() {
	            public int compare(OwlClassHolder o1, OwlClassHolder o2) {
//	                if(o1.isDeprecated()) {
//	                    if(!o2.isDeprecated()) {
//	                        return 1;
//	                    }
//	                }
//	                else if(o2.isDeprecated()) {
//	                    return -1;
//	                }
	                String browserText1 = o1.getBrowserText();
	                String browserText2 = o2.getBrowserText();
	                if(browserText1.startsWith("'")) {
	                    browserText1 = browserText1.substring(1);
	                }
	                if(browserText2.startsWith("'")) {
	                    browserText2 = browserText2.substring(1);
	                }
	                return browserText1.compareToIgnoreCase(browserText2);
	            }
	        });
	        return result;
	    }
	 	private List<OwlClassHolder> loadAllClassList(OWLAPIProject project){
	 		 List<OwlClassHolder> result = new ArrayList<OwlClassHolder>();
	 		 Stack<OwlClassHolder> sClassHolders = new Stack<OwlClassHolder>();
	 		 OwlClassHolder root = this.getRootClass(project);
	 		 sClassHolders.push(root);
	 		 while (!sClassHolders.empty()){
	 			 OwlClassHolder currentClass = sClassHolders.pop();
	 			 List<OwlClassHolder> children = this.getSubclasses(project, currentClass.getName());
	 			 Collections.reverse(children);
	 			 for (OwlClassHolder child: children){
	 				 sClassHolders.push(child);
	 			 }
	 			 result.add(currentClass);		 
	 		 }
	 		 return result;
	 	}
	 
	 	private OwlClassHolder getRootClass(OWLAPIProject project) {
	 	     OwlClassHolder holder = new OwlClassHolder();
	 	     RenderingManager rm = project.getRenderingManager();
	 	     OWLClass owlThing = project.getDataFactory().getOWLThing();
	 	     String browserText = rm.getBrowserText(owlThing);
             String name = owlThing.getIRI().toString();
             holder.setBrowserText(browserText);
             holder.setName(name);
	 	     holder.setOwlClass(owlThing);
	 	     return holder;
	 	}
	 	public MappingResult mapOntologies(String project1Name, String project2Name, MappingOptions options, UserId userId){
	 		String project1DisplayName = "";
	 		String project2DisplayName = "";
	 		MetaProjectManager mpm = MetaProjectManager.getManager();
	 		project1DisplayName = mpm.getProjectDetails(ProjectId.get(project1Name)).getDisplayName();
	 		project2DisplayName  = mpm.getProjectDetails(ProjectId.get(project2Name)).getDisplayName();
	 		int currentMappingNo = 0;
	 		MappingResult result = new MappingResult();
	 		List<OwlClassHolder> o1ClassList = null;
	 		List<OwlClassHolder> o2ClassList = null;
	 		List<OWLOntologyChange> o1ChangeList = new ArrayList<OWLOntologyChange>();
	 		List<OWLOntologyChange> o2ChangeList = new ArrayList<OWLOntologyChange>();
	 		OWLAPIProject project1 = this.getProject(project1Name);
	 		OWLAPIProject project2 = this.getProject(project2Name);
	 		o1ClassList= this.loadAllClassList(project1);
	 		o2ClassList= this.loadAllClassList(project2);
	 		this.fillRemoveAxiom(project1, o1ClassList, o1ChangeList, project2Name);
	 		this.fillRemoveAxiom(project2, o2ClassList, o2ChangeList, project1Name);
	 		OwlClassHolderComparator comparator = new OwlClassHolderComparator(options.getMatchLevel());
	 		Collections.sort(o2ClassList, comparator);
	 		for (OwlClassHolder o1Class: o1ClassList){
	 			if (o1Class.getName().equals(OWLRDFVocabulary.OWL_THING.getIRI().toString())){
	 				continue;
	 			}
	 			int pos = Collections.binarySearch(o2ClassList, o1Class, comparator);
	 			if (pos<0){
	 				continue;
	 			}
	 			ArrayList<OwlClassHolder> matchedClassList = new ArrayList<OwlClassHolder>();
	 			matchedClassList.add(o2ClassList.get(pos));
	 			for (int i=pos+1; i<o2ClassList.size(); i++){
	 				if (comparator.compare(o1Class, o2ClassList.get(i))==0){  
	 					matchedClassList.add(o2ClassList.get(i));
	 				}else{
	 					break;
	 				}
	 			}
	 			for (int i=pos-1; i>=0; i--){
	 				if (comparator.compare(o1Class, o2ClassList.get(i))==0){
	 					matchedClassList.add(o2ClassList.get(i));
	 				}else{
	 					break;
	 				}
	 			}
	 			List<Integer> existingMappingNoList = new ArrayList<Integer>();
		 		for (OwlClassHolder matchedClass: matchedClassList){
		 			if (matchedClass.getMappingNo()!=0){
		 				int index = Collections.binarySearch(existingMappingNoList,matchedClass.getMappingNo());
	 		            if (index<0){
	 		            	existingMappingNoList.add((index + 1) * -1, matchedClass.getMappingNo());
	 		            }
		 			}
		 		}
		 		if(existingMappingNoList.size()==0){
		 			currentMappingNo++;
		 			o1Class.setMappingNo(currentMappingNo);
		 			for (OwlClassHolder matchedClass: matchedClassList){
		 				matchedClass.setMappingNo(currentMappingNo);
		 			}
		 		}else{
		 			int preferredMappingNo= existingMappingNoList.get(0);
		 			o1Class.setMappingNo(preferredMappingNo);
		 			for (OwlClassHolder matchedClass: matchedClassList){
		 				matchedClass.setMappingNo(preferredMappingNo);
		 			}
		 			if (existingMappingNoList.size()>1){
			 			for (OwlClassHolder tempO1Class: o1ClassList){
			 				for (int j=1; j<existingMappingNoList.size();j++){
			 					if (tempO1Class.getMappingNo()==existingMappingNoList.get(j).intValue()){
			 						tempO1Class.setMappingNo(preferredMappingNo);
			 					}
			 				}//end for
			 			}//end for
		 			}//end if
		 		}
	 		}//end for
	 		int o1MappedClassNumber = this.fillAddAxiom(project1, o1ClassList, o1ChangeList, project2Name, project2DisplayName);
	 		int o2MappedClassNumber = this.fillAddAxiom(project2, o2ClassList, o2ChangeList, project1Name, project1DisplayName);
	 		
	 		project1.applyChanges(userId, o1ChangeList, "本体映射");
	 		project2.applyChanges(userId, o2ChangeList, "本体映射");
	 		
	 		result.setO1ClassNumber(o1ClassList.size());
	 		result.setO2ClassNumber(o2ClassList.size());
	 		result.setO1MappedClassNumber(o1MappedClassNumber);
	 		result.setO2MappedClassNumber(o2MappedClassNumber);
	 		return result;
	 	}
	 	private void fillRemoveAxiom(OWLAPIProject project, List<OwlClassHolder> classList, List<OWLOntologyChange> changeList, String refProjectName){
	 		OWLDataFactory df = project.getDataFactory();
	 		for (OwlClassHolder owlClassHolder: classList){
	 			OWLClass owlClass = owlClassHolder.getOwlClass(); 
	 			for (OWLAnnotation annotation : owlClass.getAnnotations(project.getRootOntology(), df.getRDFSComment())) {
		    		 if (annotation.getValue() instanceof OWLLiteral) {
			    		 OWLLiteral commentValue = (OWLLiteral) annotation.getValue();
			    		 String value = commentValue.getLiteral();
			    		  if (MetaItemList.isPreProjectMappingFlag(value, refProjectName)){
			    			  OWLAxiom oldAx = df.getOWLAnnotationAssertionAxiom(
			    						 owlClass.getIRI(), annotation);
			    			  RemoveAxiom removeAxiom = new RemoveAxiom(project.getRootOntology(),oldAx);
			    			  changeList.add(removeAxiom);
			    		  }
		    		 }//end if
	 			}//end for
	 			
	 		}
	 	}
	 	
	 	private int fillAddAxiom(OWLAPIProject project, List<OwlClassHolder> classList, List<OWLOntologyChange> changeList, String refProjectName, String refProjectDisplayName){
	 		int count = 0;
	 		OWLDataFactory df = project.getDataFactory();
	 		for (OwlClassHolder owlClassHolder: classList){
	 			if (owlClassHolder.getMappingNo()==0){
	 				continue;
	 			}
	 			String annotaionValue= UtilConstants.FLAG_MAPPING_NO + ": " + owlClassHolder.getMappingNo() + "; "
	 					+ UtilConstants.FLAG_REF_PROJECT_DISPLAY_NAME + ": " + refProjectDisplayName + "; "
 	 					+ UtilConstants.FLAG_REF_PROJECT_ID+": " + refProjectName ;
	 			OWLClass owlClass = owlClassHolder.getOwlClass(); 
	 			OWLAnnotation commentAnno = df.getOWLAnnotation(
	 					 df.getRDFSComment(),
	 					 df.getOWLLiteral(annotaionValue, "en"));
	 			 OWLAxiom ax = df.getOWLAnnotationAssertionAxiom(
	 					 owlClass.getIRI(), commentAnno);
	 			 AddAxiom addAxiom =  new AddAxiom(project.getRootOntology(), ax);	 
	 			 changeList.add(addAxiom);
	 			 count++;
	 		}
	 		return count;
	 	}
	 	
}
